/* 
 * Log Structure Layer Implementation
 * 
 * logrw.c
 *
 * logrw.c (to be implemented by the student) implements this API.
 * block.c implements a storage layer which offers the abstraction 
 * of a sequence of blocks of fixed size. Blocks are kept in memory.
 * Contains functions for creating, destroying, writing, reading and 
 * dumping blocks, among other. 
 *
 */

#include <stdlib.h>
#include <stdio.h>
#include "../include/logrw.h"
#include "../include/block.h"
#include <sthread.h>
#include <assert.h>
#include "logrw_util.h"

#define NUM_ARRAYS 2

typedef logrw_struct_t logrw_t;

void* thread_flip(void* logrw);
int logrw_write_block(logrw_t* log, reg_t *r, char* block);
void get_x(logrw_t *l, reg_t *r);
void get_y(logrw_t *l, reg_t *r);
unsigned valid_logrw_struct(logrw_t* log);
unsigned valid_register_no(logrw_t* log, unsigned register_no);

/*
 * logrw_struct_t: the storage abstraction of a virtual disk
 * the implementation is hidden, and should be implemented by the students
 */
struct logrw_struct_ {
	unsigned int num_reg;
	unsigned int array_dim;
	unsigned int block_dim;
	unsigned int num_arrays;
	// array de registos com dimensao num_reg;
	reg_t *registers;
	// numero de flips que restam por fazer
	sthread_sem_t sem_flip;
	//exclusao mutua de exit_flip
	sthread_mutex_t mutex_exit_flip;
	//serve para terminar a thread flip. Para terminar thread flip: exit_flip=1 e assinala-se sem_flip;
	int /* boolean*/exit_flip;
	//exclusao mutua de l->y
	sthread_mutex_t mutex_next_index;
	//exclusao mutua de l->x, wait_free_space
	sthread_mutex_t mutex_active_array;
	//x e o array activo
	int active_array;
	//y e o index do proximo bloco livre
	int next_index;
	//numero de posicoes livres no array activo
	sthread_sem_t sem_free_space;
	//numero de threads que estao bloqueadas a espera de espaco livre
	int wait_free_space;
	sthread_t sthread_flip;
	blocks_t **blocks;
	//used by flip
	char* block;
};

void get_x(logrw_t *l, reg_t *r) {
	// get x
	sthread_mutex_lock(l->mutex_active_array);
	if (r->x == l->active_array) {
		l->wait_free_space++; //numero de registos que estao para ser escritos;
		sthread_mutex_unlock(l->mutex_active_array);

		//uma thread ao passar este wait, pode significar duas coisas:
		//1) quando entrou no wait havia espacos livres no array activo logo vai escrever no array activo.
		//2)quando entrou no wait nao havia espacos livres logo bloqueou depois quando o flip torna o proximo
		//array para o array activo o flip tambem assinala os espacos livres "wait_free_space" vezes. Que e
		//sempre <= que o numero de registos.c
		sthread_sem_wait(l->sem_free_space);
		sthread_mutex_lock(l->mutex_active_array);
		l->wait_free_space--;
	}
	r->x = l->active_array;
	sthread_mutex_unlock(l->mutex_active_array);
}

void get_y(logrw_t *l, reg_t *r) {
	// get next_index
	sthread_mutex_lock(l->mutex_next_index);
	r->y = l->next_index++;
	if (l->next_index == l->array_dim)
		sthread_sem_post(l->sem_flip);
	sthread_mutex_unlock(l->mutex_next_index);
}

void getNewPosition(logrw_t *l, reg_t *r) {
	get_x(l, r);
	get_y(l, r);
}

/*
 * logrw_write_block
 *
 * r tem exclusao mutua
 *
 */
int logrw_write_block(logrw_t *l, reg_t *r, char* block) {
	int result;
	// obtem nova posicao
	getNewPosition(l, r);
	result = block_write(l->blocks[r->x], r->y, block);
	return result;
}

void flip_register(logrw_t *l, reg_t *r) {
	// deadlock when another thread is writing on same register and is blocked in sem_free_space
	// . se o trinco mutex_writing estiver bloqueado podemos ignorar, pois significa que esse
	// registo ou esta a ser copiado para o novo array, ou ja foi copiado para esse array e esta a
	// ser escrito numa posicao livre. em ambos os casos o papel do flip ja foi cumprido, isto e,
	// o registo ja se encontra no array activo
	if (0 == mutex_trylock(&(r->mutex))) {
		//se o registo ainda nao tiver sido copiado para o novo array
		if (((r->x + 1) % l->num_arrays) == l->active_array) {
			assert(block_read(l->blocks[r->x],r->y, l->block) != ERROR);
			logrw_write_block(l, r, l->block);
		}
		mutex_unlock(&(r->mutex));
	}
}

void exit_if_needed(logrw_t *l) {
	sthread_mutex_lock(l->mutex_exit_flip);
	if (l->exit_flip == 1) {
		sthread_mutex_unlock(l->mutex_exit_flip);
		sthread_exit(0);
	}
	sthread_mutex_unlock(l->mutex_exit_flip);
}

void post_times(sthread_sem_t s, int n) {
	int i;
	for (i = 0; i < n; i++)
		sthread_sem_post(s);
}

/*
 logrw_flip:
 */
void* thread_flip(void* logrw) {
	logrw_t *l = (logrw_t*) logrw;
	assert( valid_logrw_struct(l));
	int reg;
	int num_post = 0; // numero de registos que estao para ser escritos por threads que
	//ficaram bloqueadas no getx em sem_free_space antes de incrementar o x;
	while (1) {
		sthread_sem_wait(l->sem_flip);
		// testa se a thread main quer terminar a thread flip
		exit_if_needed(l);

		// altera o index da proxima posicao livre
		sthread_mutex_lock(l->mutex_next_index);
		l->next_index = 0;
		sthread_mutex_unlock(l->mutex_next_index);

		//neste momento o sem_free_space == 0; logo no getNewPosition:getX
		sthread_mutex_lock(l->mutex_active_array);
		// numero de registos que estao para ser escritos antes de incrementar o x;
		num_post = l->wait_free_space;
		l->active_array = (l->active_array + 1) % (l->num_arrays);
		sthread_mutex_unlock(l->mutex_active_array);

		/**assinala as threads que estao bloquadas no semaforo sem_free_space
		 * antes do flip alterar o array activo (l->x)
		 */

		post_times(l->sem_free_space, num_post);
		// espaco livre por default num array onde todos os NUM_REG registos estao nas NUM_REG primeiras posicoes
		//do array
		post_times(l->sem_free_space, l->array_dim - l->num_reg);

		for (reg = 0; reg < l->num_reg; reg++) {
			reg_t *r = &(l->registers[reg]);
			flip_register(l, r);
		}
		num_post = 0;
	}
	sthread_exit(0);
	return NULL;
}

/*
 * logrw_write: write a register into the next free array block, writing to the whole block
 * - log: the logrw instance
 * - register_no: the number of the register to write
 * - block: the buffer with the data to write
 *   returns: 0 if sucessful, -1 if not
 */
int logrw_write(logrw_struct_t* log, unsigned register_no, char* block) {
	assert(valid_logrw_struct(log));
	assert(valid_register_no(log,register_no));
	int result;
	reg_t *r = &(log->registers[register_no]);
	mutex_t *m = &(r->mutex);
	mutex_wrlock(m);
	//write
	result = logrw_write_block(log, r, block);
	mutex_unlock(m);
	return result;
}

/*
 * logrw_read: read a single register, reading the whole block with the
 * most up-to-date content for that register
 * - log - the logrw instance
 * - register_no: the number of the register to read
 * - block: the buffer were to copy the block containing that register content [out]
 *   returns: 0 if sucessful, -1 if not
 */
int logrw_read(logrw_struct_t* log, unsigned register_no, char* block) {
	assert(valid_logrw_struct(log));
	assert(valid_register_no(log,register_no));
	unsigned result;
	reg_t *r = &(log->registers[register_no]);
	mutex_t *m = &(r->mutex);
	mutex_rdlock(m);
	//write
	result = block_read(log->blocks[r->x], r->y, block);
	mutex_unlock(m);
	return result;
}

/*
 * logrw_print
 *
 * To change this generated comment edit the template variable "comment":
 * Window > Preferences > C/C++ > Editor > Templates.
 */

void logrw_print(logrw_t* log) {
	assert( valid_logrw_struct(log));
	printf("num_reg:%d ", log->num_reg);
	printf("array_dim:%d ", log->array_dim);
	printf("block_dim:%d ", log->block_dim);
	printf("num_arrays:%d ", log->num_arrays);
	printf("active_array:%d ", log->active_array);
	printf("next_free_index:%d ", log->next_index);
	//printf("flip_failed:%d ",log->flip_failed);
}

/*
 * block_dump: dumps general debugging information
 * such as Array with next free block, index for next free block, next block to copy
 * threads waiting on clean, and the block location for each register
 * - log - the logrw instance
 */
void logrw_dump(logrw_t* log) {
	assert( valid_logrw_struct(log));
	printf("DUMP_BEGINNING:LOGRW\n");
	logrw_print(log);
	printf("\n");
	logrw_dump_all_registers(log);
	printf("DUMP_END:LOGRW\n");
	return;
}
/*
 * logrw_dump_register: dumps the content of a specified register
 * should indicate register index, the index and array for the register block
 * and the contents of this register block (e.g. not necessary to print all the block,
 * it is enough just 8 first bytes of the block, for instance on hexadecimal representation)
 * - i: the number of the register to dump
 * - log - the logrw instance
 */
void logrw_dump_register(unsigned int i, logrw_t* log) {
	assert(valid_logrw_struct(log));
	assert(valid_register_no(log,i));
	reg_print(&(log->registers[i]));
	printf("block_dump:");
	block_dump(log->blocks[log->registers[i].x], log->registers[i].y);
	return;
}
/*
 * logrw_dump_all_registers: dumps the content of all registers
 * - log - the logrw instance
 */
void logrw_dump_all_registers(logrw_t* log) {
	assert(valid_logrw_struct(log));
	printf("DUMP_BEGINNING:ALL_REGISTERS\n");
	int i;
	for (i = 0; i < log->num_reg; i++)
		logrw_dump_register(i, log);
	printf("DUMP_END:ALL_REGISTERS\n");
	return;
}

/*
 * logrw_num_registers: gets the number of registers
 * - log - the logrw instance
 *   returns: number of the num_reg parameter that was provided when
 *   the logrw was first initialized; -1 if error
 */
int logrw_num_registers(logrw_t* log) {
	assert(valid_logrw_struct(log));
	return log->num_reg;
}

/*
 * logrw_init: create a logrw instance
 * should allocate space for all registers
 * should allocate space for blocks arrays
 * - num_reg: number of registers
 * - array_dim: size of each of the two arrays of blocks
 * - bloco_dim: size of each block
 *   returns: pointer to the logrw instance
 */

logrw_struct_t* logrw_init(unsigned int num_reg, unsigned int array_dim,
		unsigned int block_dim) {
	assert( (num_reg > 0) && (array_dim > 0) && (block_dim >0));
	int i;
	logrw_struct_t *l = (logrw_struct_t*) safeMalloc(sizeof(logrw_struct_t));
	l->num_reg = num_reg;
	l->array_dim = array_dim;
	l->block_dim = block_dim;
	l->num_arrays = NUM_ARRAYS;
	l->registers = (reg_t*) safeMalloc(sizeof(reg_t) * l->num_reg);
	l->mutex_exit_flip = sthread_mutex_init();
	l->sem_flip = sthread_sem_init(0);
	l->exit_flip = 0;
	l->mutex_active_array = sthread_mutex_init();
	l->mutex_next_index = sthread_mutex_init();
	l->active_array = 0;
	l->next_index = 0;
	l->sem_free_space = sthread_sem_init(0);
	post_times(l->sem_free_space, l->array_dim - l->num_reg);
	l->wait_free_space = 0;
	l->sthread_flip = sthread_create(thread_flip, l, 1, 1, 0);
	l->block = (char*) safeMalloc(sizeof(char) * (l->block_dim));

	l->blocks = (blocks_t**) safeMalloc(sizeof(blocks_t*) * l->num_arrays);
	for (i = 0; i < l->num_arrays; i++) {
		l->blocks[i] = block_new(l->array_dim, l->block_dim);
	}
	for (i = 0; i < l->num_reg; i++) {
		reg_init(&(l->registers[i]), i, l->active_array);
		l->next_index++;
	}
	return l;
}

/*
 * logrw_free: free the logrw
 * - log - the logrw instance
 */
void logrw_free(logrw_struct_t* l) {
	assert( l != NULL);
	int i;
	sthread_mutex_lock(l->mutex_exit_flip);
	l->exit_flip = 1;
	sthread_mutex_unlock(l->mutex_exit_flip);
	sthread_sem_post(l->sem_flip);
	void *ret;
	assert( 0 == sthread_join(l->sthread_flip, &ret));
	for (i = 0; i < l->num_reg; i++) {
		reg_free(&(l->registers[i]));
	}
	for (i = 0; i < l->num_arrays; i++) {
		block_free(l->blocks[i]);
	}
	free(l->blocks);
	free(l->block);
	sthread_mutex_free(l->mutex_exit_flip);
	sthread_mutex_free(l->mutex_active_array);
	sthread_mutex_free(l->mutex_next_index);
	sthread_sem_destroy(l->sem_free_space);
	sthread_sem_destroy(l->sem_flip);
	free(l->registers);
	free(l);

}

unsigned valid_logrw_struct(logrw_struct_t* log) {
	if (log == NULL)
		return FALSE;
	else
		return TRUE;
}

unsigned valid_register_no(logrw_struct_t *log, unsigned register_no) {
	if (0 <= register_no && register_no < log->num_reg)
		return TRUE;
	else
		return FALSE;
}
